package app

import (
	"bytes"
	"fmt"
	"gin-demo/pkg/logging"
	"gin-demo/pkg/setting"
	pb "gin-demo/proto"
	"github.com/gin-gonic/gin"
	"github.com/golang/protobuf/jsonpb"
	"github.com/golang/protobuf/proto"
	"github.com/golang/protobuf/ptypes"
	"github.com/gorilla/websocket"
	"github.com/pkg/errors"
	"net"
	"net/http"
	"reflect"
	"runtime"
	"strings"

	"gin-demo/pkg/e"
)

// context type
const (
	JSONContentType = "application/json;charset=utf8"
)

type Gin struct {
	C *gin.Context
}

// NewGin gin
func NewGin(ctx *gin.Context) *Gin {
	return &Gin{C: ctx}
}

// Response resp
type Response struct {
	Code int         `json:"code"`
	Msg  interface{} `json:"msg"`
	Data interface{} `json:"data"`
}

// resp Msg key
const (
	ResponseMsgCodeKey   = "code"
	ResponseMsgDetailKey = "detail"
)

// Error interface error
func (r *Response) Error() (msg string) {
	_, msg = r.GetMessage()
	return
}

// Response setting gin.JSON
func (g *Gin) Response(httpCode, errCode int, data interface{}) {
	g.C.JSON(httpCode, NewResponse(httpCode, errCode, data))
	return
}

// ResponseResp setting gin.JSON
func (g *Gin) ResponseResp(resp *Response) {
	g.C.JSON(resp.Code, resp)
	return
}

// ResponseErr setting gin.JSON
func (g *Gin) ResponseErr(err error) {
	if err != nil {
		if r, ok := FromResponseErr(err); ok {
			g.ResponseResp(r)
		} else {
			g.Response(http.StatusOK, e.ERROR, nil)
		}
		return
	}
}

// JSON setting gin.JSON
func (g *Gin) JSON(data proto.Message) {
	// any data
	anyData, err := ptypes.MarshalAny(data)
	if err != nil {
		logging.Error(err)
		g.ResponseErr(NewResponseErr(http.StatusOK, e.ERROR, nil))
		return
	}
	resp := &pb.Response{
		Code: http.StatusOK,
		Msg:  &pb.Msg{Code: e.SUCCESS, Detail: e.GetMsg(e.SUCCESS)},
		Data: anyData,
	}

	// marshal
	var buf = new(bytes.Buffer)
	if err := JSONHandler.Marshal(buf, resp); err != nil {
		logging.Error(err)
		g.ResponseErr(NewResponseErr(http.StatusOK, e.ERROR, nil))
		return
	}

	// resp
	g.C.Data(http.StatusOK, JSONContentType, buf.Bytes())
	return
}

// JSONOmitempty setting gin.JSON
// with omitempty
func (g *Gin) JSONOmitempty(data proto.Message) {
	// any data
	anyData, err := ptypes.MarshalAny(data)
	if err != nil {
		logging.Error(err)
		g.ResponseErr(NewResponseErr(http.StatusOK, e.ERROR, nil))
		return
	}
	resp := &pb.Response{
		Code: http.StatusOK,
		Msg:  &pb.Msg{Code: e.SUCCESS, Detail: e.GetMsg(e.SUCCESS)},
		Data: anyData,
	}

	// marshal
	var buf = new(bytes.Buffer)
	if err := JSONOmitHandler.Marshal(buf, resp); err != nil {
		logging.Error(err)
		g.ResponseErr(NewResponseErr(http.StatusOK, e.ERROR, nil))
		return
	}

	// resp
	g.C.Data(http.StatusOK, JSONContentType, buf.Bytes())
	return
}

// GetMessage code and Msg
func (r *Response) GetMessage() (code int, msg string) {
	if r.Msg == nil {
		return
	}

	// map
	if msgMap, ok := r.Msg.(map[string]interface{}); ok {
		if c, cOk := msgMap[ResponseMsgCodeKey]; cOk && c != nil {
			code, _ = c.(int)
		}
		if m, mOK := msgMap[ResponseMsgDetailKey]; mOK && m != nil {
			msg, _ = m.(string)
		}
		return
	}

	// string
	if m, ok := r.Msg.(string); ok {
		code = e.SUCCESS
		msg = m
		return
	}

	code = e.SUCCESS
	return
}

// NewResponse response to error
func NewResponse(httpCode, errCode int, data interface{}) *Response {
	msg := e.GetMsg(errCode)
	if errCode == 400 {
		dataType := reflect.TypeOf(data)
		dataValue := reflect.ValueOf(data)
		if dataType.Kind().String() == "string" {
			msg = dataValue.String()
			data = nil
		}
	}
	return &Response{
		Code: httpCode,
		Msg: map[string]interface{}{
			ResponseMsgCodeKey:   errCode,
			ResponseMsgDetailKey: msg,
		},
		Data: data,
	}
}

// NewResponseErr response to error
func NewResponseErr(httpCode, errCode int, data interface{}) error {
	// debug
	if setting.IsOutputDebug() {
		_, file, line, _ := runtime.Caller(1)
		fmt.Printf("file : %s, line(%d), code(%d) \n", file, line, errCode)
	}
	return NewResponse(httpCode, errCode, data)
}

// IsResponseErr 响应错误
func IsResponseErr(err error) bool {
	if _, ok := errors.Cause(err).(*Response); ok {
		return true
	}
	return false
}

// FromResponseErr 解析错误
func FromResponseErr(err error) (*Response, bool) {
	if r, ok := errors.Cause(err).(*Response); ok {
		return r, true
	}
	return nil, false
}

// ResponseJSON json
type ResponseJSON struct {
	Code int32       `json:"code,omitempty"` // code
	Msg  *pb.Msg     `json:"Msg,omitempty"`  // message
	Data interface{} `json:"data,omitempty"` // data
}

// ParseResponseJSON json
func ParseResponseJSON(message proto.Message, bodyBytes []byte) (res *pb.Response, err error) {
	res = &pb.Response{}
	err = jsonpb.Unmarshal(bytes.NewReader(bodyBytes), res)
	if err != nil {
		logging.Error(err)
		err = NewResponseErr(http.StatusOK, e.ERROR, nil)
		return
	}

	// 解密
	if res.Data == nil {
		return
	}
	err = ptypes.UnmarshalAny(res.Data, message)
	if err != nil {
		logging.Error(err)
		err = NewResponseErr(http.StatusOK, e.ERROR, nil)
		return
	}
	return
}

// ParseResponseJSON2 json
//func ParseResponseJSON2(message proto.Message, bodyBytes []byte) (res *ResponseJSON, err error) {
//	res = &ResponseJSON{Data: message}
//	err = json.Unmarshal(bodyBytes, res)
//	if err != nil {
//		logging.Error(err)
//		err = NewResponseErr(http.StatusOK, e.ERROR, nil)
//		return
//	}
//	return
//}

// WebsocketJSON websocket.JSON
func WebsocketJSON(conn *websocket.Conn, data proto.Message) (err error) {

	// marshal
	var buf = new(bytes.Buffer)
	if err = JSONHandler.Marshal(buf, data); err != nil {
		logging.Error(err)
		err = conn.WriteJSON(data)
		return
	}

	err = conn.WriteMessage(websocket.TextMessage, buf.Bytes())
	return
}

// IsConnCloseErr .
func IsConnCloseErr(err error) bool {
	if readErr, ok := err.(*net.OpError); ok {
		return strings.Contains(readErr.Error(), "use of closed network connection")
	}
	return false
}

func (r *Response) FailAll(code int, msg string) *Response {
	r.Code = code
	r.Msg = msg
	_, r.Data = r.GetMessage()
	return r
}
